Skip to content

Core: Sequence bounds preconditions#1100

Merged
MikaelMayer merged 4 commits into
mainfrom
feat/core-sequence-wf
May 19, 2026
Merged

Core: Sequence bounds preconditions#1100
MikaelMayer merged 4 commits into
mainfrom
feat/core-sequence-wf

Conversation

@fabiomadge

@fabiomadge fabiomadge commented May 2, 2026

Copy link
Copy Markdown
Contributor

Blocker for Laurel Seq/Array in #1073.

Bounds preconditions for Sequence.select / update / take / drop

Following the Int.SafeDiv pattern:

Op Precondition
Sequence.select 0 <= i && i < Sequence.length(s)
Sequence.update 0 <= i && i < Sequence.length(s)
Sequence.take 0 <= n && n <= Sequence.length(s)
Sequence.drop 0 <= n && n <= Sequence.length(s)

Sequence.length / empty / append / build / contains remain total.

PrecondElim picks the obligations up automatically at call sites in imperative code (via transformStmt) and in pure positions like requires / ensures / quantifier bodies / function bodies (via the synthetic $$wf procedures).

Obligations carry propertyType = "outOfBoundsAccess" (new MetaData constant, mirroring divisionByZero), flow through a new PropertyType.outOfBoundsAccess enum variant and the three metadata-to-PropertyType conversion sites, and render as "out-of-bounds-access" in SARIF output.

Incidental fix

While wiring the SARIF classification, I noticed propertyTypeToClassification in SarifOutput.lean was pre-existing dead code: vcResultToSarifResult never set properties.propertyType, so every obligation defaulted to "assert" in SARIF output. This PR wires it up, so divisionByZero and arithmeticOverflow obligations now also classify correctly in SARIF alongside the new outOfBoundsAccess.

Testing

  • StrataTest/Transform/PrecondElim.lean Test 10collectPrecondAsserts attaches outOfBoundsAccess metadata for all four partial ops plus a nested Sequence.select(Sequence.update(...)) call. Mirrors the pattern in OverflowCheckTest.lean. Complemented by per-op pretty-print snapshots (#guard_msgs) that assert the exact obligation body string — these catch regressions that preserve count and metadata tag but corrupt the obligation body (e.g. passing .Le instead of .Lt to mkSeqBoundsPrecond at a call site, changing the bound-variable name, or swapping the lower/upper bound inside mkSeqBoundsPrecond).
  • StrataTest/Languages/Core/Tests/SarifOutputTests.lean — property-type classification tests covering all five PropertyType variants.

Collateral updates to existing tests reflect the new obligations (Seq.lean) and updated requires on Sequence function signatures (ProgramEvalTests.lean). Note: the bounds obligations in Seq.lean appear as true && 0 < length(...) — the partial evaluator simplifies 0 <= 0 to true but does not further simplify true && X to X. This is a pre-existing evaluator gap, not newly introduced by this PR; the SMT solver discharges the obligation trivially.

Known downstream impact

PR #1073 (Laurel Seq/Array) emits Sequence.select / update / take / drop calls from its translation. Its T18_Sequences test uses #guard_msgs(drop info, error) on a diagnostics-only pipeline, so the test assertions should still pass syntactically — but individual sequence-manipulation programs now require bounds guards to fully verify. PR #1073 has been adjusted accordingly.

@github-actions github-actions Bot added the Core label May 2, 2026
@fabiomadge fabiomadge force-pushed the feat/core-sequence-wf branch 2 times, most recently from dacef58 to 5e268e5 Compare May 2, 2026 23:05
@fabiomadge fabiomadge marked this pull request as ready for review May 2, 2026 23:24
@fabiomadge fabiomadge requested a review from a team May 2, 2026 23:24
tautschnig
tautschnig previously approved these changes May 4, 2026
Comment thread Strata/Languages/Core/DDMTransform/FormatCore.lean Outdated
Comment thread Strata/Languages/Core/Factory.lean Outdated
Comment thread Strata/Languages/Core/Factory.lean Outdated
Comment thread Strata/Transform/PrecondElim.lean Outdated
Comment thread StrataTest/Transform/PrecondElim.lean Outdated
Comment thread StrataTest/Transform/PrecondElim.lean Outdated
Comment thread StrataTest/Transform/PrecondElim.lean Outdated
atomb
atomb previously approved these changes May 5, 2026

@atomb atomb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks nice. There's a little room to reduce repetition, however.

Comment thread Strata/DL/Imperative/CmdEval.lean Outdated
Comment thread Strata/Languages/Core/StatementEval.lean Outdated
Comment thread Strata/Languages/Core/ObligationExtraction.lean Outdated
Comment thread StrataTest/Transform/PrecondElim.lean

@tautschnig tautschnig left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Proof-coverage suggestions ("what to prove next"). These are all follow-ups, not blocking.

  • convertMetaDataPropertyType_inverse_of_classifyPrecondition — for every (fn, idx) that classifyPrecondition maps to some metaString, convertMetaDataPropertyType applied to a MetaData tagged with that string returns a non-.assert PropertyType. Concretely: there shouldn't be a gap where classifyPrecondition tags a call but convertMetaDataPropertyType silently reverts to .assert. Trivial to state, would catch the string-mismatch class of regressions the triplication enables.

  • PropertyType-to-SARIF round-trip — (propertyTypeToClassification p).fromClassification? = some p for all p : PropertyType, where fromClassification? is a matching deserializer (not yet written). Would make PropertyType the single source of truth and mechanically prune the MetaData.* string constants.

  • mkSeqBoundsPrecond_form — a structural theorem about (mkSeqBoundsPrecond x k).expr: it equals boolAnd (intLe 0 x) (k.opExpr x (seqLength s)) (or the concrete LExpr shape). Makes Test 10's off-by-one blind spot (see inline comment #2) explicit and proved rather than merely tested. Small.

  • seqSelect_denote_partial — semantic soundness of the bounds precondition with respect to Lean's List.get?: 0 ≤ i < s.length → (seqSelect s i).denote = .some (s.get! i). This is FactoryCorrect.lean territory (cf. PR #1103) rather than this PR, but worth tracking as the obvious continuation: the preconditions added here are only useful insofar as they correspond to a semantic partiality.

  • collectPrecondAsserts-spec — a purely structural theorem that for every sub-expression that is a call to a function with N preconditions, collectPrecondAsserts produces exactly N assert statements, each tagged by classifyPrecondition. Test 10 verifies counts 1, 1, 1, 1, and 2 empirically; a theorem would subsume all of that and catch e.g. a future refactor that stops visiting nested calls.

Refactoring opportunities (not blocking).

  • As noted in finding #3, consolidate MetaData.{divisionByZero,...} / propertyTypeToClassification / convertMetaDataPropertyType into a single table.
  • The new assertOutOfBoundsObligations helper in the tests is a good pattern; the pre-existing overflow/division tests in the same file (Test 1, Test 3, Test 5) could share a sibling helper, reducing per-test boilerplate uniformly.

Comment thread Strata/Transform/PrecondElim.lean Outdated
Comment thread Strata/Languages/Core/Factory.lean Outdated
Comment thread StrataTest/Transform/PrecondElim.lean
Comment thread Strata/DL/Imperative/EvalContext.lean

@MikaelMayer MikaelMayer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 Clean PR. The convertMetaDataPropertyType extraction is a good de-duplication, SeqBoundKind is a nice way to prevent nested-precondition accidents, and the SARIF property-type wiring fix is a genuine improvement (was dead code before). Test coverage is solid.

No new issues beyond what's already been flagged in existing threads.

Comment thread StrataTest/Languages/Core/Tests/GenericCallFallbackTest.lean Outdated
@fabiomadge fabiomadge force-pushed the feat/core-sequence-wf branch from bbf0d27 to 2bf64c3 Compare May 6, 2026 22:03
@fabiomadge fabiomadge requested review from MikaelMayer and joscoh May 11, 2026 12:58
@fabiomadge fabiomadge force-pushed the feat/core-sequence-wf branch from 8325c0e to dca23bd Compare May 17, 2026 13:15
fabiomadge added a commit that referenced this pull request May 17, 2026
Add `seqName : String := "s"` parameter so the helper no longer hardcodes
the input variable name in a string literal three definitions away from
the call sites that depend on it. All four current callers (`Sequence.{select,update,take,drop}`) name their `Sequence a` input `"s"` and use the
default. A future partial Sequence op with a different input name need
only pass it explicitly rather than rely on the hidden literal.

Mismatches between a function's declared inputs and the names used in its
preconditions remain caught at elaboration by `polyUneval`'s `h_precond`
free-vars check; this change only reduces the code-distance between
cause and effect.

Cherry-pick target: PR #1100. The change sits inside #1100's
`mkSeqBoundsPrecond`, so it belongs there. When #1100 lands and this
branch rebases on main, the commit will be deduplicated automatically.
MikaelMayer
MikaelMayer previously approved these changes May 18, 2026
fabiomadge added a commit that referenced this pull request May 18, 2026
Add `seqName : String := "s"` parameter so the helper no longer hardcodes
the input variable name in a string literal three definitions away from
the call sites that depend on it. All four current callers (`Sequence.{select,update,take,drop}`) name their `Sequence a` input `"s"` and use the
default. A future partial Sequence op with a different input name need
only pass it explicitly rather than rely on the hidden literal.

Mismatches between a function's declared inputs and the names used in its
preconditions remain caught at elaboration by `polyUneval`'s `h_precond`
free-vars check; this change only reduces the code-distance between
cause and effect.

Cherry-pick target: PR #1100. The change sits inside #1100's
`mkSeqBoundsPrecond`, so it belongs there. When #1100 lands and this
branch rebases on main, the commit will be deduplicated automatically.
@fabiomadge fabiomadge force-pushed the feat/core-sequence-wf branch from e2e5870 to 24db579 Compare May 18, 2026 22:24
@fabiomadge fabiomadge changed the title Core: Sequence bounds preconditions and VC-printer fallback fix Core: Sequence bounds preconditions May 18, 2026

@MikaelMayer MikaelMayer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖🔍 All previous comments appear addressed — reviewer sign-off still needed.

polyUneval is the combinator used to declare unevaluated polymorphic
functions with axioms. Unlike unaryOp and binaryOp, it had no way to
attach preconditions; callers had to hand-build the WFLFunc.

Add a 'preconditions' parameter and the matching free-vars proof
obligation (subset of the function's input names), defaulting to empty.
No behavioral change for existing callers.
Sequence.select and Sequence.update now require `0 <= i < length(s)`;
Sequence.take and Sequence.drop require `0 <= n <= length(s)`. PrecondElim
picks these up and generates VC obligations at call sites, both in
statement positions (via transformStmt) and in pure positions (via
mkContractWFProc / mkFuncWFProc) — so requires/ensures/quantifier-body
subscripts are also covered.

Obligations carry the propertyType metadata "outOfBoundsAccess" (new
MetaData constant) and flow through a new PropertyType.outOfBoundsAccess
enum variant — with matching entries in the statement-eval /
obligation-extraction / cmd-eval metadata-to-PropertyType conversion
sites — to finally render as "out-of-bounds-access" in SARIF output,
matching how divisionByZero and arithmeticOverflow are classified.

Side effect: `propertyTypeToClassification` in SarifOutput.lean was
previously dead code; `vcResultToSarifResult` never set
`properties.propertyType` so the SARIF output defaulted every obligation
to "assert". Wiring this up means divisionByZero and arithmeticOverflow
obligations now also classify correctly in SARIF — a pre-existing bug
this PR incidentally fixes.
New tests in StrataTest/Transform/PrecondElim.lean:
- Test 10:  Sequence.select in a procedure body emits the bounds assert
            (PrecondElim is unconditional — it inserts regardless of any
            surrounding requires guard; the SMT solver discharges).
- Test 10c: Sequence.select inside a requires clause triggers the
            $$wf-procedure path (mkContractWFProc).
- Test 10d: Sequence.select inside a function body triggers the
            function-body $$wf path (mkFuncWFStmts).
- Test 11:  collectPrecondAsserts attaches outOfBoundsAccess metadata
            for all four partial ops and a nested call. Mirrors
            OverflowCheckTest.lean. Also verifies Sequence.length emits
            no obligation (it is total).

New property-classification tests in
StrataTest/Languages/Core/Tests/SarifOutputTests.lean cover all five
PropertyType variants, exercising the SARIF wiring fix in commit 3.

Collateral test updates for real behavioral changes:
- StrataTest/Languages/Core/Examples/Seq.lean: expected VC output
  includes the new bounds obligations (all SMT-provable from the
  surrounding context, except the pre-existing contains_yes unknown).
- StrataTest/Languages/Core/Tests/ProgramEvalTests.lean: Sequence func
  signatures now render with the attached requires clauses.
Add `seqName : String := "s"` parameter so the helper no longer hardcodes
the input variable name in a string literal three definitions away from
the call sites that depend on it. All four current callers (`Sequence.{select,update,take,drop}`) name their `Sequence a` input `"s"` and use the
default. A future partial Sequence op with a different input name need
only pass it explicitly rather than rely on the hidden literal.

Mismatches between a function's declared inputs and the names used in its
preconditions remain caught at elaboration by `polyUneval`'s `h_precond`
free-vars check; this change only reduces the code-distance between
cause and effect.

Cherry-pick target: PR #1100. The change sits inside #1100's
`mkSeqBoundsPrecond`, so it belongs there. When #1100 lands and this
branch rebases on main, the commit will be deduplicated automatically.
@fabiomadge fabiomadge force-pushed the feat/core-sequence-wf branch from 24db579 to 2ab3b4b Compare May 19, 2026 18:25

@MikaelMayer MikaelMayer left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖🔍 All previous comments appear addressed — reviewer sign-off still needed.

@MikaelMayer MikaelMayer added this pull request to the merge queue May 19, 2026
Merged via the queue into main with commit 04d5215 May 19, 2026
35 checks passed
@MikaelMayer MikaelMayer deleted the feat/core-sequence-wf branch May 19, 2026 19:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants